package com.a0soft.gphone.aTruffleHog.di;
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * Copyright(C) 2011 
 *   Alger Lin <addre75 (at) gmail.com>
 *
 */

import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import com.a0soft.gphone.aTruffleHog.di.BtInfo.KeySet;
import com.a0soft.gphone.aTruffleHog.util.CONSTS;
import com.a0soft.gphone.aTruffleHog.util.aDebug;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;


/**Agent to collects information about wifi.*/
public class WifiInfo extends BaseInfo {
    /**Tag for log.*/
    private static final String LOG_TAG = WifiInfo.class.getSimpleName();
    /**Group of wifi capability.<p>
     * Data at this group be updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROUP_CAPABILITY = 0;
    /**Group of wifi connection.<p>
     * Data at this group not updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROP_CONNECT = 1;
    /**Group of DHCP information.<p>
     * Data at this group not updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROP_DHCP = 2;
    /**Group of wifi hotspot information.<p>
     * Data at this group not updated to server.<br>
     * Key of data at this group is the human-readable name.*/
    public static final int     GROUP_HOTSPOT = 3;
    /**Maximum count of group.*/
    private static final int    GROUP_MAX = 4;
    /**Group name.<p>
     * Elements' index follows GROUP_xxx definition*/
    private static final String GROUP_NAME[] =
    {
        "Wifi capability",  //GROUP_CAPABILITY
        "Wifi connection",  //GROP_CONNECT
        "DHCP server",      //GROP_DHCP
        "Wifi hotspot",     //GROUP_HOTSPOT
    };
    /**Format-able string of wifi configuration item.*/
    private static final String STR_CONFIG = "Wifi conf. - %s"; 
    /**Key of wifi available states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_WIFI_AVA = DeviceInfoKey.GetName(DeviceInfoKey.K.WF_AVA);
    /**Key of wifi direct available states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_WIFI_DIRECT_AVA = DeviceInfoKey.GetName(DeviceInfoKey.K.WF_DIRECT);
    /**Key of dual band supported states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_DUAL_BAND = DeviceInfoKey.GetName(DeviceInfoKey.K.WF_DUAL_BAND);
    /**Key of multicasting enabled states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_MULTI = DeviceInfoKey.GetName(DeviceInfoKey.K.WF_MULTICAST);
    /**Key of interface at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_INTERFACE = DeviceInfoKey.GetName(DeviceInfoKey.K.WF_INTERFACE);
    /**Key of wifi states at GROUP_CAPABILITY.*/
    private static final String KEY_CAPABILITY_STATE = "State";
    /**Key of SSID at GROP_CONNECT.*/
    private static final String KEY_CONNECT_ID = WifiConfHelper.KEY_ID;
    /**Key of SSID at GROP_CONNECT.*/
    private static final String KEY_CONNECT_SSID = WifiConfHelper.KEY_SSID;
    /**Key of BSSID at GROP_CONNECT.*/
    private static final String KEY_CONNECT_BSSID = WifiConfHelper.KEY_BSSID;
    /**Key of IP address at GROP_CONNECT.*/
    private static final String KEY_CONNECT_IP = "IP";
    /**Key of Mac address at GROP_CONNECT.*/
    private static final String KEY_CONNECT_MAC = "MAC";
    /**Key of supplicant state at GROP_CONNECT.*/
    private static final String KEY_CONNECT_SUPPLICANT = "Supplicant state";
    /**Key of linking speed at GROP_CONNECT.*/
    private static final String KEY_CONNECT_SPEED = "Speed";
    /**Key of server address at GROP_DHCP.*/
    private static final String KEY_DHCP_SERV = "Server";
    /**Key of 1st DNS server address at GROP_DHCP.*/
    private static final String KEY_DHCP_DNS1 = "DNS1";
    /**Key of 2nd DNS server address at GROP_DHCP.*/
    private static final String KEY_DHCP_DNS2 = "DNS2";
    /**Key of gateway address at GROUP_DHCP.*/    
    private static final String KEY_DHCP_GATEWAY = "Gateway";
    /**Key of netmask at GROUP_DHCP.*/
    private static final String KEY_DHCP_NETMASK = "Netmask";
    /**Key of lease period at GROUP_DHCP.*/
    private static final String KEY_DHCP_LEASE = "Lease period";
    /**Key of enabled state of GROUP_HOTSPOT.*/
    private static final String KEY_HOTSPOT_ENABLE = "Hotspot enabled";
    /**Separates symbol in IP address.*/
    private static final String IP_SYMB = ".";
    /**Default for IP convert.*/
    private static final int    IP_RADIX_DEFAULT = 10;
    /**Hidden class LinkProperties for wrap properties for linking.*/
    private static Class<?>     m_oClsProp = null;
    /**Hidden method in ConnectivityManager to query linking properties.*/
    private static Method       m_oMetProp = null;
    /**Hidden method in LinkProperties to get interface name of linking.*/
    private static Method       m_oMetInterface = null;
    /**Hidden method in WifiManager to get enabled status of wifi hotspot.*/
    private static Method       m_oMetAp  = null;
    /**Hidden method in WifiManager to get SSID of wifi hotspot.*/
    private static Method       m_oMetApConf = null;
    /**Hidden method in WifiManager to check if dual band supported.*/
    private static Method       m_oMetDuBand = null;
    /**Hidden method in WifiManager to enabled status of multicasting.*/
    private static Method       m_oMetMulti = null;
    /**Flag to indicates hidden API in WifiManager loaded.*/
    private static boolean      m_bLoadWifiMgr = false;
    /**Flg o indicates hidden API for LinkProperties loaded.*/
    private static boolean      m_bLoadLinkProp = false;
        
    
    /**Build information of sensors.
     * @param di        [out] Repository of device information.
     * @param context   [in] Application environment.*/
    static void
    Build(DeviceInfo di, Context context)
    {
        getWifiData(context, GROUP_CAPABILITY, di);
    }
    
    /**Get human-readable group name.
     * @param group [in] Identify of group, this parameter should be one of GROUP_xxx definition.
     * @return      Return name of group if succeeded, return null if failed.*/
    public static String getWifiDataGroupName(int group)
    {
        String  returnObj = null;
        if((group < 0) || (group >= GROUP_MAX)) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, String.format("Invalid group: %d", group));
        }
        else 
            returnObj = GROUP_NAME[group];
        return returnObj;
    }
    
    /**Get human-readable configuration name.
     * @param oConf [in] Data set of configuration.
     * @return      Return name of wifi configuration if succeeded, return null if failed.*/
    public static String getWifiConfName(HashMap<String, String> oConf)
    {
        String  returnObj = null;
        if(oConf == null) {
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without data set");
        } 
        else if(!oConf.containsKey(WifiConfHelper.KEY_SSID)) {
            returnObj = String.format(STR_CONFIG, CONSTS.UNKNOWN);
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without SSID");
        }
        else 
            returnObj = String.format(STR_CONFIG, oConf.get(WifiConfHelper.KEY_SSID));
        return returnObj;
    }
    
    /**Get wifi data by group set.
     * @param context   [in] Application environment.
     * @param group     [in] Identify of group, this parameter should be one of GROUP_xxx 
     *                  definition.
     * @param di        [out] Repository of device information. Set null if without repository to
     *                  feed.<p>
     *                  When this parameter set, only the data for server be feed.  
     * @return          Return data set if succeeded, return null if not available.*/
    private static HashMap<String, String> getWifiData(Context context, int group, DeviceInfo di)
    {
        HashMap<String, String> returnObj = null;
        WifiManager             oWifi = null;
        loadHiddenApi();
        if(context == null) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, "Without Context");
        }
        else if((oWifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE)) == null) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, "Get WiFiManager failed");
        }
        //Group for wifi capibility 
        else if(group == GROUP_CAPABILITY) {
            PackageManager      oPkg = context.getPackageManager();
            ConnectivityManager oConn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if((oPkg == null) || (oConn == null)) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, String.format("Get %s failed", ((oPkg == null)? "PackageManager": "ConnectivityManager")));
            }
            else {
                NetworkInfo                 oInfo = null;
                android.net.wifi.WifiInfo   oWifiInfo = getWifiInfo(oWifi);
                String                      szVal = null;
                int                         iSta = 0;
                returnObj = new HashMap<String, String> ();
                //Clean device information repository
                if(di != null) {                
                    di.Put(DeviceInfoKey.K.WF_DUAL_BAND, CONSTS.UNKNOWN);
                    di.Put(DeviceInfoKey.K.WF_MULTICAST, CONSTS.UNKNOWN);
                    di.Put(DeviceInfoKey.K.WF_AVA, CONSTS.UNKNOWN);
                    di.Put(DeviceInfoKey.K.WF_DIRECT, CONSTS.UNKNOWN);
                    di.Put(DeviceInfoKey.K.WF_INTERFACE, CONSTS.UNKNOWN);
                }
                //Set dual-band capability by hidden API            
                if(m_oMetDuBand != null) {
                    Object  oVal = null;
                    try {
                        oVal = m_oMetDuBand.invoke(oWifi);
                    }
                    catch(Exception ex)  {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Boolean)) {
                        returnObj.put(KEY_CAPABILITY_DUAL_BAND, oVal.toString());
                        if(di != null)
                            di.Put(DeviceInfoKey.K.WF_DUAL_BAND, Boolean.class.cast(oVal).booleanValue());
                    }
                }
                //Set multicasting capability by hidden API
                if(m_oMetMulti != null) {
                    Object  oVal = null;
                    try {
                        oVal = m_oMetMulti.invoke(oWifi);
                    }
                    catch(Exception ex)  {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof Boolean)) {
                        returnObj.put(KEY_CAPABILITY_MULTI, oVal.toString());
                        if(di != null)
                            di.Put(DeviceInfoKey.K.WF_MULTICAST, Boolean.class.cast(oVal).booleanValue());
                    }
                }
                //Set interface by hidden API
                if((m_oClsProp != null) && ((oInfo = oConn.getNetworkInfo(ConnectivityManager.TYPE_WIFI)) != null)) {
                    Object  oProp = null;
                    Object  oName = null;
                    Object  oPropSub = null;
                    Object  oNameSub = null;
                    try {
                        oProp = m_oMetProp.invoke(oConn, oInfo.getType());
                        oName = m_oMetInterface.invoke(oProp);
                        oPropSub = m_oMetProp.invoke(oConn, oInfo.getSubtype());
                        oNameSub = m_oMetInterface.invoke(oPropSub); 
                    }
                    catch(Exception ex) {
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if(oName != null) {
                        szVal = ((oNameSub != null)? String.format("%s \\ %s", oName, oNameSub): oName.toString());
                        returnObj.put(KEY_CAPABILITY_INTERFACE, szVal);
                        if(di != null)
                            di.Put(DeviceInfoKey.K.WF_INTERFACE, szVal);
                    }
                }
                //Set wifi capability by public API
                if(oPkg != null)
                {
                    String  szWifi = String.valueOf(oPkg.hasSystemFeature(PackageManager.FEATURE_WIFI));
                    String  szDirect = String.valueOf(oPkg.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT));
                    returnObj.put(KEY_CAPABILITY_WIFI_AVA, szWifi);
                    returnObj.put(KEY_CAPABILITY_WIFI_DIRECT_AVA, szDirect);
                    if(di != null) {
                        di.Put(DeviceInfoKey.K.WF_AVA, szWifi);
                        di.Put(DeviceInfoKey.K.WF_DIRECT, szDirect);
                    }
                }
                //Set wifi state
                if((di == null) && ((iSta = getWifiState(oWifi)) >= 0)) {
                    switch(iSta) {
                        case WifiManager.WIFI_STATE_DISABLED:
                            returnObj.put(KEY_CAPABILITY_STATE, "disabled");
                            break;
                        case WifiManager.WIFI_STATE_DISABLING:
                            returnObj.put(KEY_CAPABILITY_STATE, "disabling");
                            break;
                        case WifiManager.WIFI_STATE_ENABLED:
                            returnObj.put(KEY_CAPABILITY_STATE, "enabled");
                            break;
                        case WifiManager.WIFI_STATE_ENABLING:
                            returnObj.put(KEY_CAPABILITY_STATE, "enabling");
                            break;
                        case WifiManager.WIFI_STATE_UNKNOWN:
                            returnObj.put(KEY_CAPABILITY_STATE, "unknown");
                            break;
                        default:
                            returnObj.put(KEY_CAPABILITY_STATE, String.valueOf(iSta));
                            break;
                    }
                }
                //Set Mac
                if((oWifiInfo != null) && ((szVal = oWifiInfo.getMacAddress()) != null) && (szVal.length() > 0) && BtInfo.isValidMacAddress(szVal))
                    returnObj.put(KEY_CONNECT_MAC, szVal);
            }
        }
        //Group for wifi connection
        else if(group == GROP_CONNECT) {
            android.net.wifi.WifiInfo   oInfo = getWifiInfo(oWifi);
            if(di != null) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Wifi connection information not for server");
            }
            else if(oInfo == null) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Without WifiInfo");
            }
            else {
                String  szVal = null;
                int     iVal = 0;
                boolean bConn = false;
                returnObj = new HashMap<String, String> ();
                //Set ID
                if(bConn = ((iVal = oInfo.getNetworkId()) >= 0))
                    returnObj.put(KEY_CONNECT_ID, String.valueOf(iVal));
                //Set BSSID
                if(((szVal = oInfo.getBSSID()) != null) && (szVal.length() > 0))
                    returnObj.put(KEY_CONNECT_BSSID, szVal);
                //Set SSID
                if(((szVal = oInfo.getSSID())!= null) && (szVal.length() > 0))
                    returnObj.put(KEY_CONNECT_SSID, szVal);
                //Set IP
                if((iVal = oInfo.getIpAddress()) != 0)
                    returnObj.put(KEY_CONNECT_IP, convertIpToStr(iVal));
//                //Set Mac
//                if(((szVal = oInfo.getMacAddress()) != null) && (szVal.length() > 0) && BtInfo.isValidMacAddress(szVal))
//                    returnObj.put(KEY_CONNECT_MAC, szVal);
                //Set linking speed
                if(bConn && (iVal = oInfo.getLinkSpeed()) >= 0)
                    returnObj.put(KEY_CONNECT_SPEED, String.format("%d %s", iVal, android.net.wifi.WifiInfo.LINK_SPEED_UNITS));
                //Set supplicant
                if(((szVal = android.net.wifi.WifiInfo.getDetailedStateOf(oInfo.getSupplicantState()).name()) != null) && (szVal.length() > 0))
                    returnObj.put(KEY_CONNECT_SUPPLICANT, szVal);
            }
        }
        //Group for DHCP server
        else if(group == GROP_DHCP) {
            DhcpInfo                    oInfo = getDhcpInfo(oWifi);
            android.net.wifi.WifiInfo   oConn = getWifiInfo(oWifi);
            if(di != null) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "DHCP information not for server");
            }
            else if(oInfo == null) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Without DhcpInfo");
            }
            //Show DHCP information if with wifi connection only
            else if((oConn == null) || (oConn.getNetworkId() < 0)){
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Without wifi connection");
            }                
            else {
                returnObj = new HashMap<String, String> ();
                //Set server address
                if(oInfo.serverAddress != 0)
                    returnObj.put(KEY_DHCP_SERV, convertIpToStr(oInfo.serverAddress));
                //Set DNS address
                if(oInfo.dns1 != 0)
                    returnObj.put(KEY_DHCP_DNS1, convertIpToStr(oInfo.dns1));
                if(oInfo.dns2 != 0)
                    returnObj.put(KEY_DHCP_DNS2, convertIpToStr(oInfo.dns2));
                //Set gateway
                if(oInfo.gateway != 0)
                    returnObj.put(KEY_DHCP_GATEWAY, convertIpToStr(oInfo.gateway));
                //Set netmask
                returnObj.put(KEY_DHCP_NETMASK, convertIpToStr(oInfo.netmask));
                //Set lease duration
                if(oInfo.leaseDuration >= 0)
                    returnObj.put(KEY_DHCP_LEASE, String.format("%d %s", ((oInfo.leaseDuration >= 60)? (oInfo.leaseDuration / 60): oInfo.leaseDuration), ((oInfo.leaseDuration >= 60)? "min(s)": "sec(s)")));
            }
        }
        //Group for wifi hotspot
        else if(group == GROUP_HOTSPOT) {
            if(di != null) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "wifi hotspot information not for server");
            }
            else if((m_oMetAp == null) && (m_oMetApConf == null)) {
                if(aDebug.ENABLED)
                    aDebug.d(LOG_TAG, "Unable to get ata about wifi hotspot");
            }
            else {
                Object  oVal = null;
                returnObj = new HashMap<String, String> ();
                //Set wifi hotspot states
                if(m_oMetAp != null) {
                    try {
                        oVal = m_oMetAp.invoke(oWifi);
                    }
                    catch(Exception ex) { 
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if(oVal != null)
                        returnObj.put(KEY_HOTSPOT_ENABLE, oVal.toString());
                }
                //Set wifi hotspot configuration
                if(m_oMetApConf != null) {
                    try {
                        oVal = m_oMetApConf.invoke(oWifi);
                    }
                    catch(Exception ex) { 
                        if(aDebug.ENABLED)
                            aDebug.d(LOG_TAG, "Invoke failed", ex);
                    }
                    if((oVal != null) && (oVal instanceof WifiConfiguration))
                        WifiConfHelper.putConfIntoDataSet(WifiConfiguration.class.cast(oVal), returnObj);
                }
            }
        }
        else if(aDebug.ENABLED)
            aDebug.w(LOG_TAG, String.format("Invalid group: %d", group));
        return returnObj;
    }
    /**Get wifi data by group set.
     * @param context   [in] Application environment.
     * @param group     [in] Identify of group, this parameter should be one of GROUP_xxx 
     *                  definition.
     * @param di        [out] Repository of device information. Set null if without repository to
     *                  feed.<p>
     *                  This parameter is valid if group is GROUP_CAPABILITY only, because this is 
     *                  the only group with data to server. 
     * @param preview   [in] Set true if require data updated to server, set false if not. 
     * @return          Return data set if succeeded, return null if not available.*/
    public static HashMap<String, String> getWifiData(Context context, int group, boolean preview)
    {
        return getWifiData(context, group, (preview? DeviceInfo.GetInstance(context): null));
    }
    
    /**Get wifi configurations' data set.
     * @param context   [in] Application environment.
     * @return          Return arrays for configurations if succeeded, return empty array if 
     *                  without configuration, else return null if failed.<p>
     *                  Each elements in returned array is an data set for one configuration.*/
    public static ArrayList<HashMap<String, String>> getWifiConfData(Context context)
    {
        ArrayList<HashMap<String, String>>  returnObj = null;
        WifiManager                         oWifi = null;
        List<WifiConfiguration>             listConf = null;
        if(context == null) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, "Without Context");
        }
        else if((oWifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE)) == null) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, "Get WiFiManager failed");
        }
        //Handle without configuration
        else if(((listConf = getWifiConfs(oWifi)) == null) || (listConf.isEmpty())) {
            returnObj = new ArrayList<HashMap<String, String>> ();
            if(aDebug.ENABLED)
                aDebug.d(LOG_TAG, "Without WifiConfiguration");
        }
        //Handle configuration one by one
        else {
            Iterator<WifiConfiguration> oItr = listConf.iterator();
            returnObj = new ArrayList<HashMap<String, String>> ();
            while(oItr.hasNext()) {
                WifiConfiguration   oConf = oItr.next();
                if(oConf != null) {
                    HashMap<String, String> oInfo = new HashMap<String, String> ();
                    WifiConfHelper.putConfIntoDataSet(oConf, oInfo);
                    if(!oInfo.isEmpty())
                        returnObj.add(oInfo);
                }
            }
        }
        return returnObj;      
    }
    
    /**Get sorted key of data.
     * @param data  [in] Wifi data or configuration which returned from getWifiData or 
     *              getWifiConfData.
     * @return      Return list of sorted key of data if succeeded, return null if failed.*/
    public static String[] getSortedKeyOfWifiData(HashMap<String, String> data)
    {
        String      returnObj[] = null;
        Set<String> oKeys = null;
        if((data != null) && ((oKeys = data.keySet()) != null)) {
            returnObj = oKeys.toArray(new String[oKeys.size()]);
            Arrays.sort(returnObj, new KeyComp());
        }
        return returnObj;
    }
    
    /**Wrap function for get wifi information with try/catch block, to deal with exception if 
     * permission ACCESS_WIFI_STATE removed.
     * @param oWifi [in] Wifi manager.
     * @return      Return wifi information if succeeded, return null if failed.*/
    private static android.net.wifi.WifiInfo getWifiInfo(WifiManager oWifi)
    {
        android.net.wifi.WifiInfo   returnObj = null;
        if(oWifi != null) {
            try {
                returnObj = oWifi.getConnectionInfo();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }
        return returnObj;
    }
    
    /**Wrap function for get DHCP server information with try/catch block, to deal with exception 
     * if permission ACCESS_WIFI_STATE removed.
     * @param oWifi [in] Wifi manager.
     * @return      Return DHCP server information if succeeded, return null if failed.*/
    private static DhcpInfo getDhcpInfo(WifiManager oWifi)
    {
        DhcpInfo   returnObj = null;
        if(oWifi != null) {
            try {
                returnObj = oWifi.getDhcpInfo();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }
        return returnObj;
    }
    
    /**Wrap function for get wifi states with try/catch block, to deal with exception if permission
     * ACCESS_WIFI_STATE removed.
     * @param oWifi [in] Wifi manager.
     * @return      Return one of WIFI_STATE_xxx definition at WifiManager if succeeded, return 
     *              negative if failed.*/
    private static int getWifiState(WifiManager oWifi)
    {
        int returnVal = -1;
        if(oWifi != null) {
            try {
                returnVal = oWifi.getWifiState();
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, null, ex);
            }
        }
        return returnVal;
    }
    
    /**Wrap function for get wifi configurations with try/catch block, to deal with exception if 
     * permission ACCESS_WIFI_STATE removed.
     * @param oWifi [in] Wifi manager.
     * @return      Return wifi information if succeeded, return null if failed.*/
    private static List<WifiConfiguration> getWifiConfs(WifiManager oWifi)
    {
        List<WifiConfiguration> returnObj = null;
        try {
            returnObj = oWifi.getConfiguredNetworks();
        }
        catch(Exception ex) {
            if(aDebug.ENABLED)
                aDebug.w(LOG_TAG, null, ex);
        }
        return returnObj;
    }
    
    /**Convert IP address with integer format into String format.
     * @param iIp       [in] IP address with integer format.
     * @param iRadix    [in] Radix to translates IP address, this value should within 
     *                  Character.MIN_RADIX and Character.MAX_RADIX.
     * @return          Return IP address with String format.*/
    private static String convertIpToStr(int iIp, int iRadix)
    {
        StringBuffer    returnObj = new StringBuffer();
        if((iRadix < Character.MIN_RADIX) || (iRadix > Character.MAX_RADIX))
            iRadix = IP_RADIX_DEFAULT;
        returnObj.append(Integer.toString((iIp & 0xff), iRadix));
        returnObj.append(IP_SYMB);
        returnObj.append(Integer.toString(((iIp >> 8) & 0xff), iRadix));
        returnObj.append(IP_SYMB);
        returnObj.append(Integer.toString(((iIp >> 16) & 0xff), iRadix));
        returnObj.append(IP_SYMB);
        returnObj.append(Integer.toString(((iIp >> 24) & 0xff), iRadix));
        return returnObj.toString();
    }
    /**Convert IP address with integer format into String format.
     * @param iIp   [in] IP address with integer format.
     * @return      Return IP address with String format.*/
    private static String convertIpToStr(int iIp)
    {
        return convertIpToStr(iIp, IP_RADIX_DEFAULT);
    }
    
    /**Load hidden API.*/
    private static void loadHiddenApi()
    {
        //Load hidden API in WifiManager
        if(!m_bLoadWifiMgr) {
            //Load method for wifi hotspot
            try {
                m_oMetAp = WifiManager.class.getMethod("isWifiApEnabled");
                m_oMetApConf = WifiManager.class.getMethod("getWifiApConfiguration");
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            }
            //Load method for wifi capibility
            try {
                m_oMetDuBand = WifiManager.class.getMethod("isDualBandSupported");
                m_oMetMulti = WifiManager.class.getMethod("isMulticastEnabled");
            }
            catch(Exception ex) {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            }
            m_bLoadWifiMgr = true;
        }
        //Load hidden API for LinkProperties
        if(!m_bLoadLinkProp) {
            try {
                m_oClsProp = Class.forName("android.net.LinkProperties");
                m_oMetProp = ConnectivityManager.class.getMethod("getLinkProperties", Integer.TYPE);
                m_oMetInterface = m_oClsProp.getMethod("getInterfaceName");
            }
            catch(Exception ex) {
                m_oClsProp = null;
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Load API failed", ex);
            }
            m_bLoadLinkProp = true;
        }
    }
    
    
    /**Key comparator.*/
    private static class KeyComp implements Comparator<String>
    {
        /**Set of keys' priority.*/
        private static KeySet   KEYS[] = {
                       //Key name                           //Priority
            new KeySet(KEY_CAPABILITY_WIFI_AVA,             0),
            new KeySet(KEY_CAPABILITY_STATE,                1),
            new KeySet(KEY_CAPABILITY_WIFI_DIRECT_AVA,      2),
            new KeySet(KEY_CAPABILITY_DUAL_BAND,            3),
            new KeySet(KEY_CAPABILITY_MULTI,                4),
            new KeySet(KEY_CAPABILITY_INTERFACE,            5),
            new KeySet(KEY_HOTSPOT_ENABLE,                  6),
            new KeySet(WifiConfHelper.KEY_STATUS,           7),
            new KeySet(WifiConfHelper.KEY_SSID,             8), //KEY_CONNECT_SSID
            new KeySet(WifiConfHelper.KEY_ID,               9), //KEY_CONNECT_ID
            new KeySet(WifiConfHelper.KEY_BSSID,            10),//KEY_CONNECT_BSSID
            new KeySet(KEY_CONNECT_IP,                      11),
            new KeySet(KEY_CONNECT_MAC,                     12),
            new KeySet(KEY_CONNECT_SUPPLICANT,              13),
            new KeySet(KEY_CONNECT_SPEED,                   14),
            new KeySet(KEY_DHCP_SERV,                       15),
            new KeySet(KEY_DHCP_DNS1,                       16),
            new KeySet(KEY_DHCP_DNS2,                       17),
            new KeySet(KEY_DHCP_GATEWAY,                    18),
            new KeySet(KEY_DHCP_NETMASK,                    19),
            new KeySet(KEY_DHCP_LEASE,                      20),
            new KeySet(WifiConfHelper.KEY_PRIORITY,         21),
            new KeySet(WifiConfHelper.KEY_HIDDEN,           22),
            new KeySet(WifiConfHelper.KEY_KEYMGMT,          23),
            new KeySet(WifiConfHelper.KEY_PROTOCOL,         24),
            new KeySet(WifiConfHelper.KEY_PSK,              25),
            new KeySet(WifiConfHelper.KEY_AUTH,             26),
            new KeySet(WifiConfHelper.KEY_PAIRWISE_CIPHERS, 27),
            new KeySet(WifiConfHelper.KEY_GROUP_CIPHERS,    28)
        };
        /**Flag to indicates if KEYS field be sorted.*/
        private static boolean  m_bSorted = false;
        
        /**Compares its two arguments for order. Returns a negative integer, zero, or a positive 
         * integer as the first argument is less than, equal to, or greater than the second.
         * @param o1    [in] the first object to be compared.
         * @param o2    [in] the second object to be compared.
         * @return      a negative integer, zero, or a positive integer as the first argument is 
         *              less than, equal to, or greater than the second.*/
        @Override
        public int compare(String o1, String o2)
        {
            int returnVal = 0;
            //Sort key set
            if(!m_bSorted) {
                Arrays.sort(KEYS);
                m_bSorted = true;
            }
            //Handle if key is null
            if(o1 == null)
                returnVal = ((o2 == null)? 0: 1);
            else if(o2 == null)
                returnVal = -1;
            else {

                int iIdx1 = -1;
                int iIdx2 = -1;
                //Get index of keys
                iIdx1 = Arrays.binarySearch(KEYS, new KeySet(o1));
                iIdx2 = Arrays.binarySearch(KEYS, new KeySet(o2));
                //Handle if key not found
                if(iIdx1 < 0)
                    returnVal = ((iIdx2 < 0)? o1.compareToIgnoreCase(o2): 1);
                else if(iIdx2 < 0)
                    returnVal = -1;
                else if(iIdx1 == iIdx2)
                    returnVal = 0;
                else
                    returnVal = ((KEYS[iIdx1].getPrio() < KEYS[iIdx2].getPrio())? -1: 1);
            }
            return returnVal;
        }
    };
    
    /**Agent to translates WifiConfiguration into data set.*/
    private static class WifiConfHelper
    {
        /**Key of SSID.*/
        private static final String     KEY_SSID = "SSID";
        /**Key of BSSID.*/
        private static final String     KEY_BSSID = "BSSID";
        /**Key of configuration identify.*/
        private static final String     KEY_ID = "ID";
        /**Key of status.*/
        private static final String     KEY_STATUS = "Status";
        /**Key of SSID hidden states.*/
        private static final String     KEY_HIDDEN = "Hidden";
        /**key of priority.*/
        private static final String     KEY_PRIORITY = "Priority";
        /**Key of key management schemes.*/
        private static final String     KEY_KEYMGMT = "Security";
        /**Key of protocol.*/
        private static final String     KEY_PROTOCOL = "Protocol";
        /**Key of authentication algorithms.*/
        private static final String     KEY_AUTH = "Authentication";
        /**Key of WPA pairwise ciphers.*/
        private static final String     KEY_PAIRWISE_CIPHERS = "Pairwise ciphers";
        /**Key of group ciphers.*/
        private static final String     KEY_GROUP_CIPHERS = "Group ciphers";
        /**Key of WPA-PSK pre-shared key.*/
        private static final String     KEY_PSK = "PSK";
        /**Flag to provides PSK or not.<p>
         * Set true could reveals user's PSK of wifi hotspot, set false to hide PSK key.*/
        private static final boolean    SHOW_PSK = false;
        /**Hidden class EnterpriseField to wrap data set for enterprise.*/
        private static Class<?>         CLS_ENTERPRISE = null;
        /**Method to query data name in EnterpriseField.*/
        private static Method           MET_NAME = null;
        /**Method to query data value in EnterpriseField.*/
        private static Method           MET_VAL = null;
        /**List of hidden field with EnterpriseField format in WifiConfiguration.*/
        private static ArrayList<Field> FIELDS_ENTERPRICSE = null;
        
        /**Put configuration into data set.
         * @param conf  [in] Configuration.
         * @param data  [out] Data set to contains configuration.
         * @return      Return instance of data set if succeeded, return null if failed.*/
        @SuppressWarnings("unused")
        private static HashMap<String, String> putConfIntoDataSet(WifiConfiguration conf, HashMap<String, String> data)
        {
            HashMap<String, String> returnObj = null;
            loadHiddenWifiConf();
            if(conf == null)
            {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Without WifiConfiguration");
            }
            else if(data == null)
            {
                if(aDebug.ENABLED)
                    aDebug.w(LOG_TAG, "Without HashMap for data");
            }
            else
            {
                //Data for identify
                if((conf.SSID != null) && (conf.SSID.length() > 0))
                    data.put(KEY_SSID, conf.SSID);
                if((conf.BSSID != null) && (conf.BSSID.length() > 0))
                    data.put(KEY_BSSID, conf.BSSID);
                if(conf.networkId >= 0)
                    data.put(KEY_ID, String.valueOf(conf.networkId));
                //Data of status
                switch(conf.status) {
                    case WifiConfiguration.Status.CURRENT:
                        data.put(KEY_STATUS, "current");
                        break;
                    case WifiConfiguration.Status.DISABLED:
                        data.put(KEY_STATUS, "disabled");
                        break;    
                    case WifiConfiguration.Status.ENABLED:
                        data.put(KEY_STATUS, "enabled");
                        break;
                }
                //Misc data
                data.put(KEY_HIDDEN, String.valueOf(conf.hiddenSSID));
                if(conf.priority >= 0)
                    data.put(KEY_PRIORITY, String.valueOf(conf.priority));
                //Key management schemes
                if((conf.allowedKeyManagement != null) && (conf.allowedKeyManagement.length() > 0)) {
                    StringBuffer    szVal = new StringBuffer();
                    for(int a = Math.min(conf.allowedKeyManagement.length(), WifiConfiguration.KeyMgmt.strings.length) - 1; a >= 0; a --) {
                        if(conf.allowedKeyManagement.get(a)) {
                            if(szVal.length() > 0)
                                szVal.insert(0, " ");
                            szVal.insert(0, WifiConfiguration.KeyMgmt.strings[a]);
                        }
                    }
                    if(szVal.length() > 0)
                        data.put(KEY_KEYMGMT, szVal.toString());
                }
                //Protocols
                if((conf.allowedProtocols != null) && (conf.allowedProtocols.length() > 0)) {
                    StringBuffer    szVal = new StringBuffer();
                    for(int a = Math.min(conf.allowedProtocols.length(), WifiConfiguration.Protocol.strings.length) - 1; a >= 0; a --) {
                        if(conf.allowedProtocols.get(a)) {
                            if(szVal.length() > 0)
                                szVal.insert(0, " ");
                            szVal.insert(0, WifiConfiguration.Protocol.strings[a]);
                        }
                    }
                    if(szVal.length() > 0)
                        data.put(KEY_PROTOCOL, szVal.toString());
                }
                //Authentication algorithms
                if((conf.allowedAuthAlgorithms != null) && (conf.allowedAuthAlgorithms.length() > 0)) {
                    StringBuffer    szVal = new StringBuffer();
                    for(int a = Math.min(conf.allowedAuthAlgorithms.length(), WifiConfiguration.AuthAlgorithm.strings.length) - 1; a >= 0; a --) {
                        if(conf.allowedAuthAlgorithms.get(a)) {
                            if(szVal.length() > 0)
                                szVal.insert(0, " ");
                            szVal.insert(0, WifiConfiguration.AuthAlgorithm.strings[a]);
                        }
                    }
                    if(szVal.length() > 0)
                        data.put(KEY_AUTH, szVal.toString());
                }
                //Pairwise ciphers
                if((conf.allowedPairwiseCiphers != null) && (conf.allowedPairwiseCiphers.length() > 0)) {
                    StringBuffer    szVal = new StringBuffer();
                    for(int a = Math.min(conf.allowedPairwiseCiphers.length(), WifiConfiguration.PairwiseCipher.strings.length) - 1; a >= 0; a --) {
                        if(conf.allowedPairwiseCiphers.get(a)) {
                            if(szVal.length() > 0)
                                szVal.insert(0, " ");
                            szVal.insert(0, WifiConfiguration.PairwiseCipher.strings[a]);
                        }
                    }
                    if(szVal.length() > 0)
                        data.put(KEY_PAIRWISE_CIPHERS, szVal.toString());
                }
                //Group ciphers
                if((conf.allowedGroupCiphers != null) && (conf.allowedGroupCiphers.length() > 0)) {
                    StringBuffer    szVal = new StringBuffer();
                    for(int a = Math.min(conf.allowedGroupCiphers.length(), WifiConfiguration.GroupCipher.strings.length) - 1; a >= 0; a --) {
                        if(conf.allowedGroupCiphers.get(a)) {
                            if(szVal.length() > 0)
                                szVal.insert(0, " ");
                            szVal.insert(0, WifiConfiguration.GroupCipher.strings[a]);
                        }
                    }
                    if(szVal.length() > 0)
                        data.put(KEY_GROUP_CIPHERS, szVal.toString());
                }
                //Pre-shared key
                if(SHOW_PSK && (conf.preSharedKey != null) && (conf.preSharedKey.length() > 0))
                    data.put(KEY_PSK, conf.preSharedKey);
                //Data with EnterpriseField format
                if(FIELDS_ENTERPRICSE != null) {
                    Iterator<Field> oItr = FIELDS_ENTERPRICSE.iterator();
                    while(oItr.hasNext()) {
                        Field   oField = oItr.next();
                        Object  oVal = null;
                        try {
                            if(((oVal = oField.get(conf)) != null) && (CLS_ENTERPRISE.isInstance(oVal))) {
                                Object oName = MET_NAME.invoke(oVal);
                                Object oValue = MET_VAL.invoke(oVal);
                                if((oName != null) && (oValue != null))
                                    data.put(oName.toString(), oValue.toString());
                            }
                        }
                        catch(Exception ex) {
                            if(aDebug.ENABLED)
                                aDebug.w(LOG_TAG, "Get value failed", ex);
                        }
                    }
                }
                returnObj = data;
            }
            return returnObj;
        }
        
        /**Load hidden API in WifiConfiguration.*/
        private static void loadHiddenWifiConf()
        {
            //Load hidden API for EnterpriseField
            if(CLS_ENTERPRISE == null) {
                try {
                    CLS_ENTERPRISE = Class.forName("android.net.wifi.WifiConfiguration$EnterpriseField");
                    MET_NAME = CLS_ENTERPRISE.getMethod("varName");
                    MET_VAL = CLS_ENTERPRISE.getMethod("value");
                }
                catch(Exception ex) {
                    if(aDebug.ENABLED)
                        aDebug.w(LOG_TAG, null, ex);
                    CLS_ENTERPRISE = null;
                }
            }
            //Load hidden field with EnterpriseField format in WifiConfiguration
            if((FIELDS_ENTERPRICSE == null) && (CLS_ENTERPRISE != null)) {
                Field   oFields[] = WifiConfiguration.class.getDeclaredFields();
                FIELDS_ENTERPRICSE = new ArrayList<Field> ();
                for(Field oItm: oFields) {
                    if(CLS_ENTERPRISE.equals(oItm.getType()))
                        FIELDS_ENTERPRICSE.add(oItm);
                }
            }
        }
    };    
}
